package opt;

import evo.TechInit;
import gui.Console;

import ids.PlacementMove;
import ids.PlacementState;

public class HillClimbing {

	private long seed;

	private int temperatureCycles;

	private int maxTemperatureCycles;

	private int maxIdleTemperatureCycles;

	private float temperature;

	private int loopsAtEachTemperature;

	private int idleTemperatureCycles;

	private float bestFitnessSoFar;

	private float secondToTheBest;

	private PlacementState bestStateSoFar;

	private PlacementState currentState;

	private float currentFitness;

	private float nextFitness;

	private TechInit ti;

	private Console console;

	public HillClimbing() {
		this.console = new Console();
	}

	public void initialise(TechInit ti) {
		this.ti = ti;
		this.idleTemperatureCycles = 0; // fruitless outer loops

		this.seed = ti.getSeed();
		this.maxTemperatureCycles = ti.getMaxTemperatureCycles();
		this.loopsAtEachTemperature = ti.getMovesAtEachTemperature();
		this.maxIdleTemperatureCycles = ti.getMaxIdleTemperatureCycles();

		this.temperatureCycles = 0; // current number of outer loops
		this.temperature = (float) 0.0; // current temperature
		this.currentFitness = 0;
		this.nextFitness = 0;

		this.currentState = ti.getState().copy();

		this.currentState.evalStateFitness();
		this.bestFitnessSoFar = this.currentState.getFitness();
		this.bestStateSoFar = this.currentState.copy();

		this.secondToTheBest = 0;
	}

	public void doHillClimbing() {
		this.console.println("At start of doing hill climbing");
		this.reset();
		int acceptsInInnerLoop = 0;
		while (!finished()) {
			if ((temperatureCycles != 0) && (temperatureCycles % 50 == 0)) {
				this.console.print("At temperature cycle " + temperatureCycles);
				this.console.println(", the best score so far is "
						+ bestFitnessSoFar);
			}
			acceptsInInnerLoop = doInnerLoop(true);
			if (acceptsInInnerLoop > 0) {
				idleTemperatureCycles = 0;
			} else {
				idleTemperatureCycles++;
			}
			temperatureCycles++;
		}
	}

	private int doInnerLoop(boolean started) {
		PlacementMove m;
		int accepts = 0;
		currentState.setTemperatureCycles(this.temperatureCycles);
		currentState.setTemperature(this.temperature);
		for (int i = 0; i < loopsAtEachTemperature; i++) {
			m = currentState.getNextMoveAndFitness();
			currentState.setInnerLoops(i);
			if (this.acceptMove(m)) {
				accepts++;
				currentState.acceptMove(m);
				currentFitness = m.getFitness();
				if (currentFitness > bestFitnessSoFar) {
					bestFitnessSoFar = currentFitness;
					bestStateSoFar = currentState.copy();
				}
			}
			if ((started) && (this.bestFitnessSoFar > this.secondToTheBest)) {
				this.console.print("Find a better placement (score "
						+ bestFitnessSoFar + ")");
				this.console.println(" at temperature cycle "
						+ this.temperatureCycles + ", inner move " + i);
				this.secondToTheBest = this.bestFitnessSoFar;
			}
		}
		return accepts;
	}

	private boolean acceptMove(PlacementMove m) {
		this.nextFitness = m.getFitness();
		return (this.nextFitness >= this.currentFitness);
	}

	private boolean finished() {
		return (this.temperatureCycles > this.maxTemperatureCycles);
	}

	private void reset() {
		this.temperatureCycles = 0; // current number of outer loops
		this.currentFitness = 0;
		this.nextFitness = 0;

		this.currentState = this.ti.getState().copy();

		this.currentState.setTemperature(this.temperature);
		this.currentState.evalStateFitness();
		this.bestFitnessSoFar = this.currentState.getFitness();
		this.bestStateSoFar = this.currentState.copy();

		this.secondToTheBest = 0;
	}

	public void setConsole(Console console) {
		this.console = console;
	}

	public PlacementState getBestStateSoFar() {
		return this.bestStateSoFar;
	}

	public PlacementState getCurrentState() {
		return this.currentState;
	}

	public int getIteration() {
		return this.temperatureCycles;
	}

	public long getSeed() {
		return this.seed;
	}

	public void setSeed(long seed) {
		this.seed = seed;
	}

	public int getMaxTemperatureCycles() {
		return this.maxTemperatureCycles;
	}

	public void setMaxTemperatureCycles(int maxTemperatureCycles) {
		this.maxTemperatureCycles = maxTemperatureCycles;
	}

	public int getMaxIdleTemperatureCycles() {
		return this.maxIdleTemperatureCycles;
	}

	public void setMaxIdleTemperatureCycles(int maxIdleTemperatureCycles) {
		this.maxIdleTemperatureCycles = maxIdleTemperatureCycles;
	}

	public int getLoopsAtEachTemperature() {
		return this.loopsAtEachTemperature;
	}

	public void setLoopsAtEachTemperature(int loopsAtEachTemperature) {
		this.loopsAtEachTemperature = loopsAtEachTemperature;
	}
}
